#pragma once

#include <cstddef>
#include <utility>
#include <tuple>
#include <array>

namespace mdtx::online
{
    // Original from:
    // https://codereview.stackexchange.com/questions/101137/template-integer-range-version-2
    namespace detail
    {
        template <typename Int, typename Seq, Int begin, Int step, bool increase>
        struct integer_range_impl;

        template <typename Int, Int... N, Int begin, Int step>
        struct integer_range_impl<Int, std::integer_sequence<Int, N...>, begin, step, true>
        {
            using type = std::integer_sequence<Int, (begin + N * step)...>;
        };

        template <typename Int, Int... N, Int begin, Int step>
        struct integer_range_impl<Int, std::integer_sequence<Int, N...>, begin, step, false>
        {
            using type = std::integer_sequence<Int, (begin - N * step)...>;
        };

        template <typename Int, Int begin, Int end, Int step>
        struct num_step
        {
            constexpr static Int value = ((begin < end ? end - begin : begin - end) - 1) / step + 1;
        };

        template <typename Int, Int begin, Int end, Int step>
        inline constexpr Int num_step_v = num_step<Int, begin, end, step>::value;

        template <typename U, size_t... I>
        constexpr auto mk_tuple_impl(const U &arr, std::index_sequence<I...>)
        {
            return std::make_tuple(arr[I]...);
        }
    }

    template <typename Int, Int begin, Int end, Int step>
    using make_integer_range = typename detail::integer_range_impl<Int, std::make_integer_sequence<Int, detail::num_step_v<Int, begin, end, step>>, begin, step, (begin < end)>::type;

    template <size_t begin, size_t end, size_t step = 1>
    using make_index_range = make_integer_range<size_t, begin, end, step>;

    template <typename T, size_t N>
    constexpr auto mk_tuple(const std::array<T, N> &arr)
    {
        return detail::mk_tuple_impl(arr, std::make_index_sequence<N>{});
    }
}
